package com.example.springboottest.example.io.redirect.appserver.nio;

import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;

/**
 * 支持多个客户端连接，均为非阻塞
 * <p>
 * 使用NIO完成网络通信的三大核心：
 * 1、通道（Channel）：负责连接
 * java.nio.channels.Channel 接口：
 * |--SelectableChannel
 * |--SocketChannel
 * |--ServerSocketChannel    //上两个是TCP
 * |--DatagramChannel        //UDP，都是用于网络 IO
 * |--Pipe.SinkChannel
 * |--Pipi.SourceChannel
 * 2、缓冲区（Buffer）：负责数据的存取
 * 3、选择器（Selector）：是 SelectableChannel 的多路复用器，用于监控 SelectableChannel 的 IO 状况
 * SelectionKey：表示 SelectableChannel 和 Selector 之间的注册关系。每次向选择器注册通道时就会选泽一个事件（选择键）
 */
public class NIOServer {
    private Selector selector;

    public void init() throws Exception {
        //创建选择器
        this.selector = Selector.open();
        //创建SocketChannel
        ServerSocketChannel channel = ServerSocketChannel.open();
        //设置为非阻塞
        channel.configureBlocking(false);
        ServerSocket serverSocket = channel.socket();
        //连接服务器
        serverSocket.bind(new InetSocketAddress(8080));
        //注册监听accept事件
        channel.register(selector, SelectionKey.OP_ACCEPT);
    }

    public void start() throws Exception {
        while (true) {
            //此方法会阻塞，直到至少有一个已注册的事件发生
            selector.select();
            Iterator<SelectionKey> ite = this.selector.selectedKeys().iterator();
            while (ite.hasNext()) {
                SelectionKey key = ite.next();
                //从集合中移除即将处理的SelectionKey,避免重复处理
                ite.remove();
                if (key.isAcceptable())   //客户端请求连接事件
                    accept(key);
                else if (key.isReadable()) //读事件
                    read(key);
            }
        }
    }

    public void accept(SelectionKey key) throws Exception {
        ServerSocketChannel server = (ServerSocketChannel) key.channel();
        //接收连接
        SocketChannel channel = server.accept();
        //设置非阻塞
        channel.configureBlocking(false);
        //为通道注册读事件
        channel.register(this.selector, SelectionKey.OP_READ);
    }

    public void read(SelectionKey key) throws Exception {
        SocketChannel channel = (SocketChannel) key.channel();
        //创建读取缓冲区
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        channel.read(buffer);
        String request = new String(buffer.array()).trim();
        System.out.println("客户端请求:\t" + request);

        ByteBuffer outBuffer = ByteBuffer.wrap(("请求" + request + "收到").getBytes());
        //将请求回送到客户端
        channel.write(outBuffer);
    }

    public static void main(String[] args) throws Exception {
        NIOServer server = new NIOServer();
        server.init();
        server.start();
    }
}
